﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;

namespace CliFx.Analyzers.Utils.Extensions;

internal static class RoslynExtensions
{
    extension(ISymbol symbol)
    {
        public bool DisplayNameMatches(string name) =>
            string.Equals(
                // Fully qualified name, without `global::`
                symbol.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat),
                name,
                StringComparison.Ordinal
            );
    }

    extension(ITypeSymbol type)
    {
        public IEnumerable<INamedTypeSymbol> GetBaseTypes()
        {
            var current = type.BaseType;

            while (current is not null)
            {
                yield return current;
                current = current.BaseType;
            }
        }

        public ITypeSymbol? TryGetEnumerableUnderlyingType() =>
            type
                .AllInterfaces.FirstOrDefault(i =>
                    i.ConstructedFrom.SpecialType
                    == SpecialType.System_Collections_Generic_IEnumerable_T
                )
                ?.TypeArguments[0];
    }

    extension(IPropertySymbol property)
    {
        // Detect if the property is required through roundabout means so as to not have to take dependency
        // on higher versions of the C# compiler.
        public bool IsRequired() =>
            property
                // Can't rely on the RequiredMemberAttribute because it's generated by the compiler, not added by the user,
                // so we have to check for the presence of the `required` modifier in the syntax tree instead.
                .DeclaringSyntaxReferences.Select(r => r.GetSyntax())
                .OfType<PropertyDeclarationSyntax>()
                .SelectMany(p => p.Modifiers)
                .Any(m => m.IsKind((SyntaxKind)8447));
    }

    extension(Compilation compilation)
    {
        public bool IsAssignable(ITypeSymbol source, ITypeSymbol destination) =>
            compilation.ClassifyConversion(source, destination).Exists;
    }

    extension(AnalysisContext analysisContext)
    {
        public void HandleClassDeclaration(
            Action<SyntaxNodeAnalysisContext, ClassDeclarationSyntax, ITypeSymbol> analyze
        )
        {
            analysisContext.RegisterSyntaxNodeAction(
                ctx =>
                {
                    if (ctx.Node is not ClassDeclarationSyntax classDeclaration)
                        return;

                    var type = ctx.SemanticModel.GetDeclaredSymbol(classDeclaration);
                    if (type is null)
                        return;

                    analyze(ctx, classDeclaration, type);
                },
                SyntaxKind.ClassDeclaration
            );
        }

        public void HandlePropertyDeclaration(
            Action<SyntaxNodeAnalysisContext, PropertyDeclarationSyntax, IPropertySymbol> analyze
        )
        {
            analysisContext.RegisterSyntaxNodeAction(
                ctx =>
                {
                    if (ctx.Node is not PropertyDeclarationSyntax propertyDeclaration)
                        return;

                    var property = ctx.SemanticModel.GetDeclaredSymbol(propertyDeclaration);
                    if (property is null)
                        return;

                    analyze(ctx, propertyDeclaration, property);
                },
                SyntaxKind.PropertyDeclaration
            );
        }
    }
}
